<?php
/**
 * Talents come from diligence, and knowledge is gained by accumulation.
 *
 * @author: 晋<657306123@qq.com>
 * @date: 2019/8/4 23:33
 */

namespace xin\articlecollect;

use BadMethodCallException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;

/**
 * 基础类
 *
 * @package xin\articlecollect
 */
class ArticleCollect{

	/**
	 * url 规则
	 *
	 * @var array
	 */
	private static $urlRule
		= [
			'wechat'     => 'mp.weixin.qq.com',
			'csdn'       => 'blog.csdn.net',
			'jianshu'    => 'www.jianshu.com',
			'ruanyifeng' => 'www.ruanyifeng.com',
			'renminwang' => 'money.people.com.cn',
			'qqnews'     => 'new.qq.com',
			'cnblogs'    => 'www.cnblogs.com',
			'baijiahao'  => 'baijiahao.baidu.com',
			'sohu'       => 'www.sohu.com',
		];

	/**
	 * 客户端默认配置
	 *
	 * @var array
	 */
	private static $clientConfig
		= [
			'verify'  => false,
			'headers' => [
				'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3719.400 QQBrowser/10.5.3715.400',
			],
		];

	/**
	 * 静态调用处理
	 *
	 * @param string $name
	 * @param array  $arguments
	 * @return mixed
	 */
	public static function __callStatic($name, $arguments){
		if(strpos($name, "factory") === 0){
			$driver = substr($name, 7);
			return self::factory($driver, $arguments[0]);
		}
		throw new BadMethodCallException("{$name}方法不存在！");
	}

	/**
	 * 构建抓取实例
	 *
	 * @param string $driver
	 * @param array  $options
	 * @return \xin\articlecollect\AbsArticleCollect
	 */
	public static function factory($driver, array $options = []){
		if(stripos($driver, '\\') !== 0){
			$driver = ucwords(str_replace(['-', '_'], ' ', $driver));
			$driver = str_replace(' ', '', $driver);
			$driver = "\\xin\\articlecollect\\driver\\{$driver}";
		}

		if(!class_exists($driver)){
			throw new \LogicException("文章抓取驱动不存在：{$driver}", 40400);
		}

		return new $driver($options);
	}

	/**
	 * 根据url进行解析
	 *
	 * @param string $url
	 * @return array
	 * @throws \xin\articlecollect\ArticleCollectException
	 */
	public static function url($url){
		$driver = self::parseUrlToDriver($url);

		$client = new Client(self::$clientConfig);
		try{
			$response = $client->get($url);

			return $driver->parse($response->getBody()->getContents());
		}catch(RequestException $e){
			if($e->hasResponse()){
				$httpCodeMessage = [
					200 => '服务器成功返回请求的数据。',
					201 => '新建或修改数据成功。',
					202 => '一个请求已经进入后台排队（异步任务）。',
					204 => '删除数据成功。',
					400 => '发出的请求有错误，服务器没有进行新建或修改数据的操作。',
					401 => '用户没有权限（令牌、用户名、密码错误）。',
					403 => '用户得到授权，但是访问是被禁止的。',
					404 => '发出的请求针对的是不存在的记录，服务器没有进行操作。',
					406 => '请求的格式不可得。',
					410 => '请求的资源被永久删除，且不会再得到的。',
					422 => '当创建一个对象时，发生一个验证错误。',
					500 => '服务器发生错误，请检查服务器。',
					502 => '网关错误。',
					503 => '服务不可用，服务器暂时过载或维护。',
					504 => '网关超时。',
				];
				$httpStatusCode = $e->getResponse()->getStatusCode();
				throw new ArticleCollectException("解析失败：{$httpCodeMessage[$httpStatusCode]}");
			}elseif($e instanceof ConnectException){
				throw new ArticleCollectException("解析失败：网络连接失败({$e->getMessage()})");
			}

			throw new ArticleCollectException("解析失败：{$e->getMessage()}");
		}
	}

	/**
	 * 根据解析url获取驱动
	 *
	 * @param string $url
	 * @return \xin\articlecollect\AbsArticleCollect
	 * @throws \xin\articlecollect\ArticleCollectException
	 */
	public static function parseUrlToDriver($url){
		$driver = self::parseUrlToDriverName($url);
		if(empty($driver)){
			throw new ArticleCollectException("未找到合适的文章抓取驱动", 40400);
		}

		return self::factory($driver);
	}

	/**
	 * 根据解析url获取驱动名称
	 *
	 * @param string $url
	 * @return string
	 */
	private static function parseUrlToDriverName($url){
		$urlInfo = parse_url($url);
		foreach(self::$urlRule as $name => $rule){
			if(is_string($rule)){
				$rule = explode('|', $rule);
			}

			if(is_array($rule)){
				if(in_array($urlInfo['host'], $rule)){
					return $name;
				}
			}else{
				if(call_user_func($rule, $urlInfo) === true){
					return $name;
				}
			}
		}

		return null;
	}

	/**
	 * 设置默认的请求配置
	 *
	 * @param array $config
	 */
	public static function setClientConfig(array $config){
		self::$clientConfig = array_merge(self::$clientConfig, $config);
	}

}
